package com.phychips.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;

import com.phychips.common.OnBytesAvailableListener;
import com.phychips.rcp.iBluetoothEvent;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.AsyncTask;

public class BluetoothTransceiver {
    private static final boolean D = true;
    private static final String ClassName = "BluetoothApi";
    private static iBluetoothEvent mBluetoothEvent = null;

    private static BluetoothAdapter mAdapter = null;
    private String m_btDestAddr = null;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;

    private static boolean mAutoread = false;

    // Constants that indicate the current connection state
    public static final int STATE_NONE = 0; // we're doing nothing
    public static final int STATE_LISTEN = 1; // now listening for incoming
    // connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing
    // connection
    public static final int STATE_CONNECTED = 3; // now connected to a remote
    // device

    // Well known SPP UUID (will *probably* map to RFCOMM channel 1 (default) if
    // not in use);
    // see comments in onResume().
    private static final UUID SPP_UUID = UUID
            .fromString("00001101-0000-1000-8000-00805F9B34FB");

    final private static byte SCP_HEADER = (byte) 0xaa;
    final private static byte SCP_TAIL = (byte) 0x55;
    final private static byte SCP_MIN_LEN = 4;

    final private static byte RCP_PKT_PRAMBL = (byte) 0xBB;
    private byte RCP_PKT_ENDMRK = (byte) 0x7E;
    private byte RCP_PRAMBL_LEN = 1;
    private byte RCP_HEADER_LEN = 4;
    private byte RCP_ENDMRK_LEN = 1;
    private byte RCP_HEADEND_LEN = (byte) (RCP_PRAMBL_LEN + RCP_HEADER_LEN + RCP_ENDMRK_LEN);
    // private byte RCP_CRC_LEN = 2;
    private byte RCP_CRC_LEN = 0;
    private byte RCP_HEADEND_CRC_LEN = (byte) (RCP_HEADEND_LEN + RCP_CRC_LEN);

    private List<Byte> m_incomingRcp = new ArrayList<Byte>();
    private List<Byte> m_incomingScp = new ArrayList<Byte>();
    public static final int MAX_BUF_SIZE = 1024;
    private int m_SleepTime = 200;

    private int comMode = 0;
    private static final int MODE_NONE = 0;
    private static final int MODE_RCP = 1;
    private static final int MODE_SCP = 2;

    final public static int HANDLER_MODE_SR9_CONNECT = (byte) 0x01;
    final public static int HANDLER_MODE_SR9_DISCONNECT = (byte) 0x02;
    final public static int HANDLER_MODE_RFID_TIMEOUT = (byte) 0x03;
    final public static int HANDLER_MODE_SR9_BATTERY_LOW = (byte) 0x04;
    final public static int HANDLER_MODE_RCP_RECEIVE = (byte) 0x05;
    final public static int HANDLER_MODE_RCP_RECEIVE_REST = (byte) 0x06;

    final public static byte CMD_CONNECT = (byte) 0xe9;
    final public static byte CMD_DISCONNECT = (byte) 0xea;
    final public static byte CMD_TIMEOUT = (byte) 0xeb;
    final public static byte CMD_BATTERY_LOW = (byte) 0xec;
    final public static byte CMD_UART_ERR = (byte) 0xed;

    public BluetoothTransceiver(BluetoothAdapter adapter) {
        mAdapter = adapter;
    }

    public void setBluetoothEvent(iBluetoothEvent e) {
        mBluetoothEvent = e;
    }

    public void AutoreadStart() {
        mAutoread = true;
    }

    public void AutoreadStop() {
        mAutoread = false;
    }

    public boolean verifyDestAddr(BluetoothDevice device) {
        String name = device.getName();
        if (name == null)
            return false;

        if (name.equalsIgnoreCase("SR9_SmartReader")
                || name.equalsIgnoreCase("TagonReader")) {
            System.out.println(ClassName + " device name = " + name);
            return true;
        }

        return false;
    }

    public String getPairedAddress() {
        String address = null;

        Set<BluetoothDevice> pairedDevice = mAdapter.getBondedDevices();
        if (pairedDevice.size() > 0) {
            for (BluetoothDevice device : pairedDevice) {
                if (D)
                    System.out.println(device.getAddress());
                if (D)
                    System.out.println(device.getName());

                if (verifyDestAddr(device) == true) {
                    address = device.getAddress();
                    break;
                }
            }
        }

        return address;
    }

    public boolean isPairedAddress(String address) {
        boolean ret = false;
        Set<BluetoothDevice> pairedDevice = mAdapter.getBondedDevices();
        if (pairedDevice.size() > 0) {
            for (BluetoothDevice device : pairedDevice) {
                if (D)
                    System.out.println(device.getAddress());
                if (D)
                    System.out.println(device.getName());

                if (verifyDestAddr(device) == true) {
                    address = device.getAddress();
                    ret = true;
                    break;
                }
            }
        }

        return ret;
    }

    public String getDiscoveredAddress(Intent i) {
        String address = null;

        try {
            BluetoothDevice device = i
                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

            if (D)
                System.out.println(device.getAddress());
            if (D)
                System.out.println(device.getName());

            if (verifyDestAddr(device) == true) {
                // stopDiscovery();
                address = device.getAddress();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return address;
    }

    public String getTargetAddress() {
        return m_btDestAddr;
    }

    public void setTargetAddress(String address) {
        m_btDestAddr = address;
    }

    public void stopDiscovery() {
        if (mAdapter.isDiscovering()) {
            if (D)
                System.out.println(ClassName + " stopDiscovery");

            mAdapter.cancelDiscovery();
        }
    }

    public void startDiscovery() {
        // If we're already discovering, stop it
        if (mAdapter.isDiscovering()) {
            mAdapter.cancelDiscovery();
        }

        m_btDestAddr = null;

        System.out.println(ClassName + " startDiscovery");

        // Request discover from BluetoothAdapter
        mAdapter.startDiscovery();
    }

    public synchronized void disconnect() {
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }

        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        setState(STATE_NONE);
    }

    public synchronized void connect(String address) {
        BluetoothDevice device = mAdapter.getRemoteDevice(address);

        if (D)
            System.out.println("connect to: " + device);

        // Cancel any thread attempting to make a connection
        if (mState == STATE_CONNECTING) {
            if (mConnectThread != null) {
                mConnectThread.cancel();
                mConnectThread = null;
            }
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        // Start the thread to connect with the given device
        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        setState(STATE_CONNECTING);
    }

    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        private String mSocketType;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
            // mSocketType = true;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                // if (secure) {
                tmp = device.createRfcommSocketToServiceRecord(SPP_UUID);
                // }
                // else
                // {
                // tmp = device.createInsecureRfcommSocketToServiceRecord(
                // MY_UUID_INSECURE);
                // }
            } catch (IOException e) {
                if (D)
                    System.out.println("Socket Type: " + mSocketType
                            + "create() failed" + e);
            }
            mmSocket = tmp;
        }

        public void run() {
            if (D)
                System.out.println("BEGIN mConnectThread SocketType:"
                        + mSocketType);
            setName("ConnectThread" + mSocketType);

            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    if (D)
                        System.out.println("unable to close() " + mSocketType
                                + " socket during connection failure" + e2);
                }
                connectionFailed();
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice, mSocketType);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                if (D)
                    System.out.println("close() of connect " + mSocketType
                            + " socket failed" + e);
            }
        }
    }

    /**
     * Start the ConnectedThread to begin managing a Bluetooth connection
     *
     * @param socket The BluetoothSocket on which the connection was made
     * @param device The BluetoothDevice that has been connected
     */
    public synchronized void connected(BluetoothSocket socket,
                                       BluetoothDevice device, final String socketType) {
        if (D)
            System.out.println("connected, Socket Type:" + socketType);

        // Cancel the thread that completed the connection
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        // Start the thread to manage the connection and perform transmissions
        mConnectedThread = new ConnectedThread(socket, socketType);
        mConnectedThread.start();

        // Send the name of the connected device back to the UI Activity
//	if (mBluetoothEvent != null)
//	    mBluetoothEvent.onConnected();

        setState(STATE_CONNECTED);
    }

    /**
     * Set the current state of the chat connection
     *
     * @param state An integer defining the current connection state
     */
    private synchronized void setState(int state) {
        if (D)
            System.out.println("setState() " + mState + " -> " + state);
        mState = state;

        // Give the new state to the Handler so the UI Activity can update
        // mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state,
        // -1).sendToTarget();
    }

    /**
     * Indicate that the connection attempt failed and notify the UI Activity.
     */
    private void connectionFailed() {
        if (D)
            System.out.println("Unable to connect device");

        // Send a failure message back to the Activity
        if (mBluetoothEvent != null)
            mBluetoothEvent.onDisconnected();
    }

    /**
     * Indicate that the connection was lost and notify the UI Activity.
     */
    private void connectionLost() {

        if (D)
            System.out.println("Device connection was lost");

        // Send a failure message back to the Activity
        if (mBluetoothEvent != null)
            mBluetoothEvent.onDisconnected();
    }

    private void parseScp(byte[] scp) {
        boolean converted = false;
        byte[] ba = new byte[scp.length + 2];
        ba[0] = (byte) 0xbb;
        ba[1] = (byte) 0x04;
        // ba[2] = (byte) scp[1]; // command
        ba[3] = (byte) 0x00;
        ba[4] = (byte) scp[2]; // cmd
        for (int i = 5; i < ba.length - 1; i++) {
            ba[i] = scp[i - 2];
        }
        ba[ba.length - 1] = (byte) 0x7e;

        int command = scp[1];

        switch (command) {

            case HANDLER_MODE_SR9_CONNECT:
                ba[2] = (byte) CMD_CONNECT; // command
                converted = true;
                break;
            case HANDLER_MODE_SR9_DISCONNECT:
                ba[2] = (byte) CMD_DISCONNECT; // command
                converted = true;
                break;
            case HANDLER_MODE_RFID_TIMEOUT:
                ba[2] = (byte) CMD_TIMEOUT; // command
                converted = true;
                break;
            case HANDLER_MODE_SR9_BATTERY_LOW:
                ba[2] = (byte) CMD_BATTERY_LOW; // command
                converted = true;
                break;
            default:
                for (int i = 0; i < scp.length; i++) {
                    if (D)
                        System.out.printf("%02X ", (int) (scp[i] & 0xff));
                }
                break;
        }

        if (converted) {
            notifyBytesAvailable(ba);
        }
    }

    /**
     * This thread runs during a connection with a remote device. It handles all
     * incoming and outgoing transmissions.
     */
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket, String socketType) {
            if (D)
                System.out.println("create ConnectedThread: " + socketType);
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                if (D)
                    System.out.println("temp sockets not created" + e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            if (D)
                System.out.println("BEGIN mConnectedThread");

            byte[] buffer = new byte[1024];
            int recvlen;
            int timer = 0;

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    // Read from the InputStream
                    recvlen = mmInStream.read(buffer);

                    for (int i = 0; i < recvlen; i++) {
                        System.out.print(Integer
                                .toHexString((int) (buffer[i] & 0xff) + 0x100)
                                .substring(1).toUpperCase(Locale.US));
                    }

                    if (recvlen > 0) {
                        // System.out.printf("===>> RecvData=");
                        for (int i = 0; i < recvlen; i++) {
                            // System.out.printf("%02X ", rbuf[i]);
                            if (m_incomingRcp.size() == 0
                                    && m_incomingScp.size() == 0) {
                                switch (buffer[i]) {
                                    case RCP_PKT_PRAMBL:
                                        comMode = MODE_RCP;
                                        timer = 0;
                                        break;
                                    case SCP_HEADER:
                                        comMode = MODE_SCP;
                                        timer = 0;
                                        break;
                                    default:
                                        continue;
                                }
                            }

                            if (comMode == MODE_SCP) {
                                synchronized (this) {
                                    m_incomingScp.add(buffer[i]);

                                    if (m_incomingScp.size() >= SCP_MIN_LEN) {
                                        if (m_incomingScp.get(2) > 255) {
                                            System.out
                                                    .println(" 2. SCP processInputBuffer: clear");
                                            m_incomingRcp.clear();
                                            m_incomingScp.clear();
                                            comMode = MODE_NONE;
                                            break;
                                        }

                                        if (m_incomingScp.size() > SCP_MIN_LEN) {
                                            if (m_incomingScp.size() == ((int) (m_incomingScp
                                                    .get(2) & 0xff) + SCP_MIN_LEN)) {
                                                if (m_incomingScp
                                                        .get((int) (m_incomingScp
                                                                .get(2) & 0xff)
                                                                + SCP_MIN_LEN
                                                                - 1) == SCP_TAIL) {
                                                    System.out.println(" ");

                                                    byte[] ba = new byte[m_incomingScp
                                                            .size()];

                                                    int baIndex = 0;

                                                    for (Byte b : m_incomingScp
                                                            .toArray(new Byte[]{})) {
                                                        ba[baIndex++] = b
                                                                .byteValue();
                                                    }

                                                    parseScp(ba);

                                                    System.out
                                                            .println("SCP Received...");

                                                    m_incomingRcp.clear();
                                                    m_incomingScp.clear();
                                                    comMode = MODE_NONE;
                                                    break;
                                                } else {
                                                    System.out
                                                            .println(" 3. SCP processInputBuffer: clear");
                                                    System.out.print(" ");

                                                    m_incomingRcp.clear();
                                                    m_incomingScp.clear();
                                                    comMode = MODE_NONE;
                                                    break;
                                                }
                                            }
                                        }

                                    }
                                }
                            }

                            if (comMode == MODE_RCP) {
                                synchronized (this) {
                                    m_incomingRcp.add(buffer[i]);

                                    if (m_incomingRcp.size() >= RCP_HEADEND_CRC_LEN) {
                                        if (m_incomingRcp.get(4) > 255) {
                                            System.out
                                                    .println(" 2. processInputBuffer: clear");
                                            m_incomingRcp.clear();
                                            m_incomingScp.clear();
                                            comMode = MODE_NONE;
                                            break;
                                        }

                                        if (m_incomingRcp.size() == ((int) (m_incomingRcp
                                                .get(4) & 0xff) + RCP_HEADEND_LEN)) {
                                            if (m_incomingRcp
                                                    .get((int) (m_incomingRcp
                                                            .get(4) & 0xff)
                                                            + RCP_PRAMBL_LEN
                                                            + RCP_HEADER_LEN) == RCP_PKT_ENDMRK) {
                                                System.out.println(" ");

                                                byte[] ba = new byte[m_incomingRcp
                                                        .size()];

                                                int baIndex = 0;

                                                for (Byte b : m_incomingRcp
                                                        .toArray(new Byte[]{}))
                                                    ba[baIndex++] = b
                                                            .byteValue();

                                                System.out
                                                        .println("RCP Received...");

                                                notifyBytesAvailable(ba);
                                                m_incomingRcp.clear();
                                                m_incomingScp.clear();
                                                comMode = MODE_NONE;
                                                break;
                                            } else {
                                                System.out
                                                        .println(" 3. processInputBuffer: clear");
                                                System.out.print(" ");
                                                m_incomingRcp.clear();
                                                m_incomingScp.clear();
                                                comMode = MODE_NONE;
                                                break;
                                            }
                                        }

                                    }

                                }
                            }
                        }
                        // System.out.printf("\n");
                        // Handler_SendEvent(rbuf, recvlen);

                    }
                    System.out.println("");

                    // Send the obtained bytes to the UI Activity
                    // mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,
                    // -1, buffer).sendToTarget();

                    Thread.sleep(m_SleepTime);
                    timer++;
                    if ((timer > (1000 / m_SleepTime))
                            && (comMode != MODE_NONE)) {
                        m_incomingRcp.clear();
                        m_incomingScp.clear();
                        comMode = MODE_NONE;
                    }
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    if (D)
                        System.out.println("disconnected" + e);

                    m_incomingRcp.clear();
                    m_incomingScp.clear();

                    connectionLost();
                    break;
                }

            }
        }

        /**
         * Write to the connected OutStream.
         *
         * @param buffer The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

                // Share the sent message back to the UI Activity
                // mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,
                // buffer).sendToTarget();
            } catch (IOException e) {
                if (D)
                    System.out.println("Exception during write" + e);
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                if (D)
                    System.out.println("close() of connect socket failed");
            }
        }
    }

    /**
     * Write to the ConnectedThread in an unsynchronized manner
     *
     * @param out The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED)
                return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(out);
    }

    private OnBytesAvailableListener bytesAvailableListener = null;

    private void notifyBytesAvailable(byte[] rxData) {
        for (Byte b : rxData) {
            System.out.print(Integer
                    .toHexString((int) (b.byteValue() & 0xff) + 0x100)
                    .substring(1).toUpperCase(Locale.US));
        }

        int type = rxData[1];
        int command = rxData[2];
        int payload = rxData[5];

        int temp = 0;

        if (mAutoread)
            temp = 1;

        System.out.printf("mAutoread = %d, [%02X]", (int) temp, payload);

        if (type == 0x02 && command == 0x22 && mAutoread == true) {
            new requestAutoread().execute(0);
        } else if (type == 0x01 && command == 0x28 && mAutoread == true) {
            new requestAutoread().execute(0);
        } else if (type == 0x01 && command == (byte) 0xFF && payload == 0x15 && mAutoread == true) {
            System.out.printf("Fail...");
            new requestAutoread().execute(0);
            return;
        }

        if (bytesAvailableListener != null) {
            bytesAvailableListener.onBytesAvailable(rxData);
        }

    } // End notifyBytesAvailable()

    public void registerBytesAvailableListener(OnBytesAvailableListener listener) {
        bytesAvailableListener = listener;
    } //

    public class requestAutoread extends AsyncTask<Integer, Void, Void> {
        protected Void doInBackground(Integer... param) {
            System.out.print("requestAutoread: ");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            //byte[] ba = new byte[] { (byte) 0xBB, 0x00, 0x27, 0x00, 0x03, 0x22,
            //(byte) 0xFF, (byte) 0xFF, 0x7E };

            byte[] ba = new byte[]{(byte) 0xBB, 0x00, 0x22, 0x00, 0x00, 0x7E};

            if (mAutoread)
                write(ba);

            for (Byte b : ba) {
                System.out.printf("%02X ", b.byteValue());
            }
            System.out.println();

            return null;
        }
    }
}
